/*  dreic -- a compiler for Drei
 *
 *  $Id: Scanner.scala 3030 2006-10-26 16:17:41Z dubochet $
 */

package dreic

import java.io.{InputStream, IOException}

// return bad when we get errors !!!!
/**
 * This class implements the scanner of the Zwei compiler.
 */
class Scanner(in: InputStream) {
  import Tokens._
  import scala.collection.mutable.HashMap;
  
  private val EOF_CH = java.lang.Character.MAX_VALUE;
  private var ch = ' ';
  private var line: Int = 1;
  private var column: Int = 0;
  var startpos: Int = 0;
  private val buf = new StringBuffer();
  var chars: String = _;
  var token: Token = BAD;


  
  private val keywords = new HashMap[String, Token]();
  keywords("class")     = CLASS;
  keywords("val")	= DVAL;
  keywords("extends")	= EXTENDS;
  keywords("while")	= WHILE;
  keywords("def")	= DEF;
  keywords("Int")	= INT;
  keywords("var")	= VAR;
  keywords("set")	= SET;
  keywords("printInt")	= PRINTINT;
  keywords("printChar")	= PRINTCHAR;
  keywords("do")	= DO;
  keywords("if")	= IF;
  keywords("true")	= TRUE;
  keywords("false")	= FALSE;
  keywords("this")	= THIS;
  keywords("readInt")	= READINT;
  keywords("readChar")	= READCHAR;
  keywords("new")	= NEW;
  keywords("return")	= RETURN;
  keywords("else")	= ELSE;
  
nextToken;

  def representation = {
    val representation = token.toString();
    if (token == NUMBER || token == IDENT || token == STRING)
      representation + "(\"" + chars + "\")";
    else
      representation
  }
  
  private def nextCh: Unit = {
    ch match {
      case EOF_CH =>
        return
      case '\n' =>
        column = 1;
        line = line + 1
      case _ =>
        column = column + 1
    }
    //Mettre a jour la position
    startpos = Position.encode(line, column);

    try {
      ch = readCh;
/*      oldch = if ((oldch == '\r') && (ch == '\n')) readCh else ch;
      ch = if (oldch == '\r') '\n' else oldch*/
    }
    catch {
      case e: IOException =>
	Report.fail(startpos, e.getMessage());
    }
  }

  private def readCh: char = {
    val c = in.read();
    if (c < 0) EOF_CH else c.asInstanceOf[Char]
  }

  
  def nextToken: Unit = {
    while ((ch == ' ') || (ch == '/') || (ch == '\n') || (ch == '\t') || (ch == '\f')){	
		while ((ch == ' ') || (ch == '\n') || (ch == '\t') || (ch == '\f') ) nextCh;
		while (ch == '/'){
			nextCh;
			if (ch == '/') {
			  while (ch != '\n' && ch != EOF_CH) nextCh;
			  nextCh;
	                } else {
	                  token = QUOT;
	                  return;
	                }
		}
    }
    // initialize the position of the current token
    startpos = Position.encode(line, column);
    // read the current token
    token = readToken
  }
  
  private def readToken: Token = ch match {
    // reconnaitre les STRINGS
	case '"' => {	nextCh;
			buf.setLength(0);
			while ((ch != '"') && (ch != EOF_CH) && (ch != '\n')){
				buf.append(ch);
				nextCh;
			}
			if (ch!='"'){
				Report.error(startpos ,"* ERROR: Chaine non terminee");
				BAD;
				readToken;
			}else{
				chars = buf.toString();
				nextCh;
				STRING;
			}
		}
    // ************************
    
	case EOF_CH => EOF;
	
    // reconnaitre les symboles
	case '(' => nextCh; LPAREN;
	case ')' => nextCh; RPAREN;
	case '{' => nextCh; LBRACE;
	case '}' => nextCh; RBRACE;
	case ';' => nextCh; POINTV;
	case ',' => nextCh; VIRGU;
	case '.' => nextCh; POINT;
	case '=' => nextCh; if (ch=='=') { nextCh; EGAL;} else AFFECT;
	case '<' => nextCh; if (ch=='=') { nextCh; PTOUEG;} else PETIT;
	case '>' => nextCh; if (ch=='=') { nextCh; GROUEG;} else GRAND;
	case '+' => nextCh; PLUS;
	case '-' => nextCh; MOINS;
	case '!' => nextCh; if (ch=='=') { nextCh; NEGAL} else NEG;
	case '*' => nextCh; PROD;
	case '/' => nextCh; QUOT;
	case '%' => nextCh; PERCENT;
	case ':' => nextCh; DPOINT;
	case '&' => nextCh; if (ch=='&') { nextCh; AND} else {Report.error("* ERROR: Expected &, found \'"+ch+"\'"); nextCh; BAD;}
	case '|' => nextCh; if (ch=='|') { nextCh; OR} else {Report.error("* ERROR: Expected |, found \'"+ch+"\'"); nextCh; BAD;}
    // ************************

	case others =>

    // reconnaitre les nombres
	if (ch=='0') {
		buf.setLength(0);
		buf.append(ch);
		chars = buf.toString();
		nextCh;
		return NUMBER;
	}else if (Character.isDigit(ch) && ch!='0') {
	    buf.setLength(0);
	    while (Character.isDigit(ch)){
	      buf.append(ch);
	      nextCh;
	    }
	    chars = buf.toString();
	    return NUMBER;
    // ***********************


    // reconnaitre les identifiants et les KEYWORDS
	}else if (Character.isLetter(ch)){
	  buf.setLength(0);
	  while (Character.isLetterOrDigit(ch) || (ch=='_')){
	    buf.append(ch);
	    nextCh;
	  }
	  chars = buf.toString();
	  keywords.get(chars) match{
	    case None => IDENT;
	    case Some(atoken) => atoken;
	  }
    // *****************************************

    // ******* PAS RECONNU **********
	}else{
	  Report.error("* ERROR: illegal character found \'"+ch+"\'");
	  nextCh;
	  BAD;
	}
    // *************************
  }  
}
